#include "Engine.h"

#include <concrete/TestNetworkEvent.h>

#include <utils/Log.h>
#include <utils/DynamicClass.h>

#include <concrete/PhysicsConstraints.h>
#include <concrete/Entity.h>

//#include <concrete/EventSystem.h>


//Event contain data and otherwise act as identification only
//Actions have an apply() method which actually does something
//Actions can be bound to events via Rules. Rules are bound to Events, and then
// invoke()d when the event is raised. A Rule may simply execute an Action, or
// it may contain some Logic before deciding on what Action to apply. The Logic
// may be dynamic or hard-coded.
/*
Event --> Rule --> Action ---|
<----------------------------|

class Rule
{
public:
	virtual void invoke() = 0;
};

class DynamicRule : public Rule
{
	LogicExpression<Rule> m_Logic;
public:
	virtual void invoke()
	{
		Rule* pResultRule = m_Logic.evaluate();
		pResultRule->apply();
	}
};

//Logic-less Rule, just applies an action without thinking
class SimpleRule : public Rule
{
	Action* m_pAction;
public:
	SimpleRule( Action* pAction ) : m_pAction(pAction) {}
	virtual void invoke()
	{
		m_pAction->apply();
	}
};

class GravityRule : public Rule
{
	Entity
public:
	virtual void invoke()
	{
		
	}
};
*/

//You know what would be awesome? Being able to switch game 'modes' by simply
// replacing all of the Rules. BUT, to go even further, we could use this also
// to change from, say, playing a level to editing a level, or editing a model!


//If all game-logic occurs in Rules bound to Events, then replicating a series
// of Events being raised should replicate the game. This is the basis for
// networking and game-replays.

//Gravity should be added as a Rule in response to the Tick event.
//e.g.,:
//NOTE: EventManager::raiseEvent( Event& ) raises the event immediately,
//      with no queueing system used. This makes the event system much like
//      an entry-point system, where code (Rules) can be 'plugged in' to
//      entry-points (Events).
/*
	[Engine.cpp]
	main loop {
		if (time to tick) {
			EventManager::raiseEvent( BasicEvent("Tick") );
			
			for each entity
				entity.update();
				EventManager::raiseEvent( EntityEvent(entity, "Update") );
			...
		}
		...
	}
	
	[Level.cpp?]
	init() {
		EventManager::addRule( EntityEvent("Update"), GravityRule() );
		or
		EventManager::addRule( EntityEvent("Update"), ApplyForceRule() );
		...
	}
	
	GravityRule : public Rule
	{
		Vector m_vGravityForce;
	public:
		virtual void invoke( EntityEvent* triggerEvent )
		{
			Entity* pEntity = triggerEvent->getEntity();
			pEntity->getPoint()->applyForce( m_vGravityForce );
		}
		
	};
*/

//Rules can only be attached to Events with which they are compatible.
/*
class Event : public DynamicClass
{
public:
	
};

class GravityRule : public Rule
{
	Vector m_vGravity;
public:
	virtual void attachTo(
	
	virtual void invoke( Event* pEvent )
	{
		pEvent->applyAction( "addForce", TypedDynamicVar<Vector>( m_vGravity ) );
	}
};
*/


//TODO: Remove this hack when done testing
//static GfxModel model1;

Engine::Engine()
{
	
	//TODO: These pointers to dynamic memory should really be replaced with
    //      something like the exception-safe boost::shared_ptr< >.
	
	//Load the renderer plugin
	m_RendererPluginterface.loadPlugin( "WindowsOpenGLRenderer" );
	m_pRenderer = m_RendererPluginterface.createInstance( "WindowsOpenGLRenderer" );
	
	//Get the singleton Multiplayer instance
	m_pMultiplayer = Multiplayer::get();
	
	//Load the input plugin
	m_InputPluginterface.loadPlugin( "WindowsAPIInput" );
	m_pInput = m_InputPluginterface.createInstance( "WindowsAPIInput" );
	
	//Create the InputBinder instance
	m_pInputBinder = new InputBinder<ClientEvent>( m_pInput );
	
	m_pWorld = new World();
	
}

Engine::~Engine()
{
	delete m_pWorld;
	
	delete m_pInputBinder;
	
	delete m_pInput;
	m_InputPluginterface.unloadPlugin( "WindowsAPIInput" );
	
	delete m_pRenderer;
	m_RendererPluginterface.unloadPlugin( "WindowsOpenGLRenderer" );
}

void Engine::init( String sTitle, unsigned iWidth, unsigned iHeight, unsigned iBitsPerPixel, bool bFullscreen )
{
	
	m_pRenderer->init( sTitle, iWidth, iHeight, iBitsPerPixel, bFullscreen );
	
	//TODO: Replace this as it's a hack for now
    //      -----------------------------------
    /*
	std::cout << "Enter h to host, or any other key to join: ";
    
    char c;
    std::cin >> c;
    
    if (c == 'h')
        m_pMultiplayer->host(8, 60000);
    else {
        
        std::cout << "Enter host address: ";
        
        String sHostAddress;
        std::cin >> sHostAddress;
        
        if (sHostAddress == "l")
            m_pMultiplayer->join("localhost", 60000, 0);
        else
            //TODO: Eventually change client port from auto assign (0) to a constant if don't need to run two clients on same computer
            m_pMultiplayer->join(sHostAddress.c_str(), 60000, 0);
    
    }
    */
    
    m_pMultiplayer->host(8, 60000);
    
    //std::cout << "Multiplayer initialised, press enter to begin running the game" << std::endl;
    //std::cin.get();
    //std::cin.get();
    //      -----------------------------------
    
	//m_pInputBinder->bindKeyDownToEvent( 'A', PlayerInputEvent(
	//          PlayerInputEvent::ACTION_START, PlayerInputEvent::ACTION_LEFT ) );
	if ( !m_pMultiplayer->isServer() ) {
		TestClientEvent* e = new TestClientEvent( "Mouse moved" );
		m_pInputBinder->bindMotionToEvent( e );
	}
	
	//Create a simple GfxModel for testing
	/*GfxModel::Vertex* v1 = &model1.addVertex( GfxModel::Vertex(Vector(0.2, 0, 0), RGBAColor::YELLOW, Vector(0, 0, 0), Vector(0, 0, 1)) );
	GfxModel::Vertex* v2 = &model1.addVertex( GfxModel::Vertex(Vector(0, 0.2), RGBAColor::WHITE, Vector(0, 0, 0), Vector(0, 0, 1)) );    
	GfxModel::Vertex* v3 = &model1.addVertex( GfxModel::Vertex(Vector(0.2, 0.2, 0), RGBAColor::BLUE, Vector(0, 0, 0), Vector(0, 0, 1)) );
	model1.addTriangle( GfxModel::Triangle(v1, v2, v3) );
	*/
	
	//Test of the new logging system (which is not 'quite' usable yet)
	report::error << "Error: Only a test error ;)" << std::endl;
	report::warning.redirect( "warnings.txt" );
	report::warning << "Warning: Test warning." << std::endl;
	report::debug << "Test debug value = " << 10.0 << std::endl;
	
	//Test of the DynamicClass system
	//-------------------------------
	//DynamicClass construction
	DynamicClass dcEntity( "Entity" );
	//NOTE: The explicit <String> type here is only necessary to distinguish it
	//      from a char*.
	dcEntity.addVar<String>( "name", "TestEntity1" );
	dcEntity.addVar( "energy", 5.16 );
	
	//DynamicVar access
	dcEntity.setVar<String>( "name", "ModifiedTestEntity1" );
	//NOTE: Reference access
	dcEntity.getVar<double>( "energy" ) += 1.0;
	//NOTE: No explicit typing for stream operators!
	std::cout << dcEntity.getVar("name") << " of type "
	          << dcEntity.getType() << " has an energy rating of "
			  << dcEntity.getVar("energy") << std::endl;
	
	//Equality comparison operators
	//Here we manually create a copy of dcEntity in a different sequence in
	// order to properly test the equality comparison operators.
	DynamicClass dcEntity2( "Entity" );
	dcEntity2.addVar<double>( "energy" ) = 6.16;
	dcEntity2.addVar<String>( "name", "ModifiedTestEntity1" );
	std::cout << "dcEntity2 == dcEntity? "
	          << (dcEntity2 == dcEntity) << std::endl;
	std::cout << "dcEntity2 != dcEntity? "
	          << (dcEntity2 != dcEntity) << std::endl;
	dcEntity2.addVar<double>( "power", 3.141592 );
	std::cout << "dcEntity2 == dcEntity? "
	          << (dcEntity2 == dcEntity) << std::endl;
	
	VisibleEntity* e = m_pWorld->addVisibleEntity( VisibleEntity( "entity1", Vector(0, 0, 0) ) );
	GfxModel* pEntityModel = e->getModel();
	
	dcEntity.addRealVar( "model1", *pEntityModel );
	GfxModel& m = dcEntity.getVar<GfxModel>( "model1" );
	Vertex* v1 = m.addVertex( Vertex(Vector(0, 0, 0), RGBAColor::ORANGE, Vector(0, 0, 0), Vector(0, 0, 1)) );
	Vertex* v2 = m.addVertex( Vertex(Vector(0, 0.1), RGBAColor::PINK, Vector(0, 0, 0), Vector(0, 0, 1)) );
	Vertex* v3 = m.addVertex( Vertex(Vector(0.1, 0.1, 0), RGBAColor::BROWN, Vector(0, 0, 0), Vector(0, 0, 1)) );
	m.addTriangle( Triangle(v1, v2, v3) );
	//-------------------------------
	
	std::cout << "pEntityModel = " << pEntityModel << std::endl;
	std::cout << "pEntityModel->getTexture() = " << pEntityModel->getTexture() << std::endl;
	/*
	Vertex* ev1 = pEntityModel->addVertex( Vertex(Vector(0, 0, 0), RGBAColor::ORANGE, Vector(0, 0, 0), Vector(0, 0, 1)) );
	Vertex* ev2 = pEntityModel->addVertex( Vertex(Vector(0, 0.1), RGBAColor::PINK, Vector(0, 0, 0), Vector(0, 0, 1)) );
	Vertex* ev3 = pEntityModel->addVertex( Vertex(Vector(0.1, 0.1, 0), RGBAColor::BROWN, Vector(0, 0, 0), Vector(0, 0, 1)) );
	pEntityModel->addTriangle( Triangle(ev1, ev2, ev3) );*/
	PhysicsPoint* pPoint = e->getPoint();
	pPoint->applyForce( Vector(0.00001, 0.0) );
	
	VisibleEntity* e2 = m_pWorld->addVisibleEntity( VisibleEntity( "entity2", Vector(0, 0, 0) ) );
	GfxModel* pEntityModel2 = e2->getModel();
	
	Vertex V1(Vector(0.2, 0, 0), RGBAColor::YELLOW, Vector(0, 0, 0), Vector(0, 0, 1));
	Vertex V2(Vector(0, 0.2, 0), RGBAColor::WHITE, Vector(0, 0, 0), Vector(0, 0, 1));
	Vertex V3(Vector(0.2, 0.2, 0), RGBAColor::BLUE, Vector(0, 0, 0), Vector(0, 0, 1));
	
	Vertex* ev21 = pEntityModel2->addVertex( V1 );
	Vertex* ev22 = pEntityModel2->addVertex( V2 );
	Vertex* ev23 = pEntityModel2->addVertex( V3 );
	
	if ( 0 == pEntityModel2->addTriangle( Triangle(ev21, ev22, ev23) ) )
		std::cout << "Triangle addition failed." << std::endl;
	
	PhysicsPoint* pPoint2 = e2->getPoint();
	pPoint2->applyForce( Vector(-0.00001, 0.0) );
	
	/* TODO: This is nearly usable
	         Just need to work out network integration with events and also
	          finalise EventManager
	//Load core RuleSets
	PlayerController playerControlRules;
	playerControlRules.addRulesTo( m_pEventManager );
	...
	//Load custom RuleSets (plugins)
	//while ( there are RuleSets to load ) {
		RuleSetPluginterface.loadPlugin( "BaseCTFRules" );
		RuleSet* pRuleSet = RuleSetPluginterface.createInstance("BaseCTFRules");
		//TODO: Need to use Component<RuleSet> to check for dependencies!
		pRuleSet->addRulesTo( m_pEventManager );
		delete pRuleSet;
	//}
	*/
}

void Engine::start()
{
	
	std::cout << "Engine starting" << std::endl;
	
	m_bRunning = true;
	while (m_bRunning) {
		
		//Update the renderer (skipping the frame if the function returns true)
 		if (m_pRenderer->update())
             continue;
 		//Make sure the renderer is currently active
 		if (!m_pRenderer->isActive())
             continue;
 		
 		//Check if the renderer has recieved a quit message
 		if (m_pRenderer->isQuit())
             m_bRunning = false;
		
		if ( true /* time to do a tick */ ) {
			//m_pWorld->update();
		}
		
		if ( m_pInput ) {
			m_pInput->update();
			
			if ( m_pInput->getKeyDown( VK_ESCAPE ) )
				m_bRunning = false;
				
			if ( m_pInput->getKeyDown( VK_SPACE ) ) {
				if ( !m_pMultiplayer->isServer() ) {
					TestClientEvent e( "A cool client event." );
					e.activate();
				}
			}
			if ( m_pInput->getKeyDown( 'S' ) ) {
				if ( m_pMultiplayer->isServer() ) {
					TestServerEvent e( "A cool server event." );
					e.activate();
				}
			}
			
			if ( m_pInputBinder )
				m_pInputBinder->update();
			
		}
		
		m_pMultiplayer->checkForPackets();
		
		//m_pRenderer->drawGfxModel( model1 );
		
		m_pWorld->update();
		m_pWorld->draw();
		
		//m_pAudio->update();
		//m_pWorld->draw();
		m_pRenderer->renderScene();
		
	}
	
}

